package flca.mda.api.util;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import mda.annotation.jpa.Column;
import mda.annotation.jpa.Id;
import mda.annotation.jpa.JoinColumn;
import mda.annotation.jpa.JoinTable;
import mda.annotation.jpa.ManyToMany;
import mda.annotation.jpa.ManyToOne;
import mda.annotation.jpa.OneToMany;
import mda.annotation.jpa.OneToOne;
import mda.annotation.jpa.Table;
import mda.type.ApplicationBaseType;
import mda.type.IDtoType;
import mda.type.IEntityType;
import mda.type.IServiceType;

import org.apache.commons.beanutils.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import flca.mda.codegen.CodegenConstants;
import flca.mda.codegen.data.DataStore;
import flca.mda.codegen.data.ITemplate;
import flca.mda.codegen.data.SubsValue;
import flca.mda.codegen.helpers.AdditionalGeneratesHelper;
import flca.mda.codegen.helpers.AnnotationsHelper;
import flca.mda.codegen.helpers.FilenameHelper;
import flca.mda.codegen.helpers.SubClassesHelper;
import flca.mda.common.api.helpers.ImportHelper;

public class TypeUtils {

	public static Logger logger = LoggerFactory.getLogger(TypeUtils.class);

	protected static NameUtils nu = new NameUtils();
	protected static ValidatorUtils vu = new ValidatorUtils();
	private static FilenameHelper fnh = new FilenameHelper();

	public static String NL = "\n";
	public static String ID = "id";
	public static String ID_SUFFIX = "Id";
	public static String S2 = "  ";
	public static String S4 = S2 + S2;
	public static String S6 = S2 + S2 + S2;

	private static final String NUMERIC_FIELDS = "int integer short long double float bigdecimal ";
	private static final String INT_FIELDS = "int integer short long";
	private static final String DEC_FIELDS = "double float bigdecimal ";
	private static final String DATE_FIELDS = "java.util.Date java.sql.Timestamp ";

	public static Map<Class<?>, Class<?>> CONCRETE_TYPES = null;
	public static Set<String> COLLECTION_TYPES = null;

	public TypeUtils() {
	}
	
	/**
	 * This will convert the given object to an instance of the given class. We
	 * need this method because the model class was loaded a different
	 * classloader.
	 * 
	 * @param aFromObject
	 * @param toClass
	 * @return
	 */
	public Object cast(Object aFromObject, Class<?> toClass) {
		try {
			Class<?> targetClz = Class.forName(toClass.getName());
			Object result = targetClz.newInstance();
			BeanUtils.copyProperties(result, aFromObject);
			return result;
		} catch (Exception e) {
			logger.warn("error casting " + aFromObject.getClass().getName() + " : " + e);
			return null;
		}
	}

	

	public Class<?> getType(Field aField) {
		return aField.getType();
	}

	/**
	 * Returns the primitive or class name for the given Type. Class names will
	 * be added as imports to the GenModel's ImportManager, and the imported
	 * form will be returned.
	 */
	public String getTypeName(Field aField) {
		return getTypeName(aField.getType(), getGenericType(aField));
	}
	
	/**
	 * Returns the primitive or class name for the given Type. Class names will
	 * be added as imports to the GenModel's ImportManager, and the imported
	 * form will be returned.
	 */
	public String getTypeName(FieldWrapper aField) {
		return getTypeName(aField.getType(), getGenericType(aField));
	}	

	/**
	 * Returns the primitive or class name for the given Type. Class names will
	 * be added as imports to the GenModel's ImportManager, and the imported
	 * form will be returned.
	 */
	public String getTypeName(Class<?> aClass) {
		if (!aClass.isPrimitive()) {
			ImportHelper.addImport(aClass);
		}

		if (isCollection(aClass)) {
			return aClass.getSimpleName() + "<?" + ">";
		} else {
			return aClass.getSimpleName();
		}
	}

	/**
	 * Returns the primitive or class name for the given Type. Class names will
	 * be added as imports to the GenModel's ImportManager, and the imported
	 * form will be returned.
	 */
	public String getTypeName(Class<?> aClass, Class<?> aGenericType) {
		if (!aClass.isPrimitive()) {
			ImportHelper.addImport(aClass);
		}
		if (aGenericType != null && !aGenericType.isPrimitive()) {
			ImportHelper.addImport(aGenericType);
		}

		if (isCollection(aClass)) {
			if (aGenericType != null) {
				return aClass.getSimpleName() + "<" + aGenericType.getSimpleName() + ">";
			} else {
				return aClass.getSimpleName() + "<?>";
			}
		} else {
			String result = aClass.getSimpleName();
			return result;
		}
	}

	/**
	 * Returns the primitive or class name for the given Type. Class names will
	 * be added as imports to the GenModel's ImportManager, and the imported
	 * form will be returned.
	 */
	public String getImportedConcreteType(Class<?> aClass, Class<?> aGenericType) {
		Class<?> concreteClass = getConcreteClass(aClass);

		if (!concreteClass.isPrimitive()) {
			ImportHelper.addImport(concreteClass);
		}
		if (aGenericType != null && !aGenericType.isPrimitive()) {
			ImportHelper.addImport(aGenericType);
		}

		if (isCollection(aClass)) {
			if (aGenericType != null) {
				return concreteClass.getSimpleName() + "<" + aGenericType.getSimpleName() + ">";
			} else {
				return concreteClass.getSimpleName() + "<?>";
			}
		} else {
			return aClass.getSimpleName();
		}
	}

	public String getGenericTypeName(Field aField) {
		Type gentyp = aField.getGenericType();
		if (gentyp instanceof ParameterizedType) {
			ParameterizedType partyp = (ParameterizedType) gentyp;
			Type[] fieldArgTypes = partyp.getActualTypeArguments();
			if (fieldArgTypes.length == 1) {
				Class<?> fieldArgClass = (Class<?>) fieldArgTypes[0];
				ImportHelper.addImport(fieldArgClass);
				return fieldArgClass.getSimpleName();
			} else {
				return "field " + aField + " has more then 1 generic types";
			}
		}
		return "field " + aField + " is not a generic type";
	}

	/**
	 * return the generic type of the given field or null if this is not a generic type
	 * @param aField
	 * @return
	 */
	public Class<?> getGenericType(Field aField) {
		Type gentyp = aField.getGenericType();
		if (gentyp instanceof ParameterizedType) {
			ParameterizedType partyp = (ParameterizedType) gentyp;
			Type[] fieldArgTypes = partyp.getActualTypeArguments();
			if (fieldArgTypes.length == 1) {
				Class<?> fieldArgClass = (Class<?>) fieldArgTypes[0];
				ImportHelper.addImport(fieldArgClass);
				return fieldArgClass;
			} else {
				return null;
			}
		}
		return null;
	}

	/**
	 * return the generic type of the given field or null if this is not a generic type
	 * @param aField
	 * @return
	 */
	public Class<?> getGenericType(FieldWrapper aField) {
		Type gentyp = aField.getGenericType();
		if (gentyp instanceof ParameterizedType) {
			ParameterizedType partyp = (ParameterizedType) gentyp;
			Type[] fieldArgTypes = partyp.getActualTypeArguments();
			if (fieldArgTypes.length == 1) {
				Class<?> fieldArgClass = (Class<?>) fieldArgTypes[0];
				ImportHelper.addImport(fieldArgClass);
				return fieldArgClass;
			} else {
				return null;
			}
		}
		return null;
	}	
	/**
	 * return the generic type of the given field or the actual type if this is not a generic type
	 * @param aField
	 * @return
	 */
	public Class<?> getGenericOrActualType(Field aField) {
		if (aField == null) {return null;}
		
		Type gentyp = aField.getGenericType();
		if (gentyp instanceof ParameterizedType) {
			ParameterizedType partyp = (ParameterizedType) gentyp;
			Type[] fieldArgTypes = partyp.getActualTypeArguments();
			if (fieldArgTypes.length == 1) {
				Class<?> fieldArgClass = (Class<?>) fieldArgTypes[0];
				ImportHelper.addImport(fieldArgClass);
				return fieldArgClass;
			} else {
				return null;
			}
		} else {
			ImportHelper.addImport(aField.getType());
			return aField.getType();
		}
		
	}

	public boolean isPrimitive(Field aField) {
		return aField.getType().isPrimitive();
	}

	public boolean isSimpleField(Field aField) {
		return isSimpleField(aField.getType());
	}

	public boolean isSimpleField(Class<?> aClass) {
		if (aClass == null) {
			return true;
		} else if (isCollection(aClass)) {
			return false;
		} else {
			return aClass.isPrimitive() || aClass.getName().startsWith("java")
					|| aClass.isEnum();
		}
	}

	public boolean isManyToOneField(Field aField) {
		if (aField != null) {
			return AnnotationsHelper.hasAnnotation(aField, ManyToOne.class);
		} else {
			return false;
		}
	}

	public boolean isOneToManyField(Field aField) {
		if (aField != null) {
			return AnnotationsHelper.hasAnnotation(aField, OneToMany.class);
		} else {
			return false;
		}
	}

	public boolean isManyToManyField(Field aField) {
		if (aField != null) {
			return AnnotationsHelper.hasAnnotation(aField, ManyToMany.class);
		} else {
			return false;
		}
	}

	public boolean isOneToOneField(Field aField) {
		if (aField != null) {
			return AnnotationsHelper.hasAnnotation(aField, OneToOne.class);
		} else {
			return false;
		}
	}

	public boolean isRelation(Field aField) {
		return isManyToManyField(aField) || isManyToOneField(aField) || isOneToOneField(aField) || isOneToManyField(aField);
	}
	
	public boolean isBooleanType(Class<?> type) {
		return "boolean".equals(type.getSimpleName().toLowerCase());
	}

	public boolean isComposite(Field aField) {
		return (isManyToOneField(aField) || isOneToManyField(aField));
	}

	public boolean isNumeric(Field aField) {
		return isNumeric(aField.getType());
	}

	public boolean isNumeric(Class<?> aClass) {
		if (isSimpleField(aClass)) {
			String typename = aClass.getName();
			return NUMERIC_FIELDS.indexOf(typename.toLowerCase()) >= 0;
		} else {
			return false;
		}
	}

	public boolean isStringType(Class<?> type) {
		return "String".equals(type.getSimpleName());
	}

	/**
	 * return true if the given class is int Integer long Long short or Short
	 * @param type
	 * @return
	 */
	public boolean isIntType(Class<?> type) {
		if (isSimpleField(type)) {
			String typename = type.getName().toLowerCase();
			if (typename.startsWith("java.lang")) {
				typename = typename.substring(10);
			}
			return (INT_FIELDS.indexOf(typename) >= 0);
		} else {
			return false;
		}
	}

	/**
	 * return true if the given class is double Double float Floao or BigDecimal
	 * @param type
	 * @return
	 */
	public boolean isDecType(Class<?> type) {
		if (isSimpleField(type)) {
			String typename = type.getName().toLowerCase();
			if (typename.startsWith("java.lang") || typename.startsWith("java.math")) {
				typename = typename.substring(10);
			}
			return (DEC_FIELDS.indexOf(typename) >= 0);
		} else {
			return false;
		}
	}

	/**
	 * return true if the given class is double Double float Floao or BigDecimal
	 * @param type
	 * @return
	 */
	public boolean isDateType(Class<?> type) {
		if (isSimpleField(type)) {
			String typename = type.getName();
			return (DATE_FIELDS.indexOf(typename) >= 0);
		} else {
			return false;
		}
	}
	
	public boolean isDate(Field aField) {
		return isDate(getType(aField));
	}

	public boolean isDate(Class<?> type) {
		return "Date".equals(type.getSimpleName());
	}

	
	public boolean isTimestamp(Class<?> type) {
		return "Timestamp".equals(type.getSimpleName());
	}

	public boolean isTime(Class<?> type) {
		return "Time".equals(type.getSimpleName());
	}

	public boolean isBoolean(Class<?> type) {
		return "boolean".equals(type.getSimpleName().toLowerCase());
	}

	public boolean isInteger(Class<?> type) {
		return "Integer".equals(type.getSimpleName());
	}
	public boolean is_int(Class<?> type) {
		return "int".equals(type.getSimpleName());
	}

	public boolean isDouble(Class<?> type) {
		return "Double".equals(type.getSimpleName());
	}
	public boolean is_double(Class<?> type) {
		return "double".equals(type.getSimpleName());
	}

	public boolean isLong(Class<?> type) {
		return "Long".equals(type.getSimpleName());
	}
	public boolean is_long(Class<?> type) {
		return "long".equals(type.getSimpleName());
	}

	// public boolean isBlob(Type type) {
	// return "Byte[]".equals(type.getName());
	// }

	// public boolean isCurrency(Property property) {
	// return "Currency".equals(property.getType().getName());
	// }
	//
	// public boolean isPercentage(Property property) {
	// return "Percentage".equals(property.getType().getName());
	// }
	//
	// public boolean isText(Property property) {
	// return "Text".equals(property.getType().getName());
	// }
	//
	// public boolean isImage(Property property) {
	// return "Image".equals(property.getType().getName());
	// }
	//
	// public boolean isLink(Property property) {
	// return "Link".equals(property.getType().getName());
	// }
	//
	// public boolean isDecimal(Property property) {
	// return "Decimal".equals(property.getType().getName());
	// }


	public String getDefaultValue(Field aField) {
		return getDefaultValue(aField.getType());
	}

	public String getDefaultValue(FieldWrapper aField) {
		return getDefaultValue(aField.getType());
	}
	
	public String getDefaultValue(Class<?> aClass) {
		String result = DataStore.getInstance().getSectionValue("java.default.value", aClass.getSimpleName());
		if (result != null && result.trim().length() == 0) {
			result = "null";
		}
		return result;
	}

	public boolean hasDefaultValue(Class<?> aClass) {
		String dv = getDefaultValue(aClass);
		return dv != null && dv.length() > 0;
	}

	public Class<?>[] getAllSuperTypes(Class<?> aClass) {
		List<Class<?>> result = new ArrayList<Class<?>>();

		getAllSuperTypes(aClass, result);
		getAllInterfaces(aClass, result);
		
		Class<?> types[] = new Class[0];
		return result.toArray(types);
	}

	public Class<?>[]  getAllInterfaces(Class<?> aClass) {
		List<Class<?>> result = new ArrayList<Class<?>>();
		getAllInterfaces(aClass, result);
		Class<?> types[] = new Class[0];
		return result.toArray(types);
	}
	
	private void getAllInterfaces(Class<?> aClass, List<Class<?>> aResult) {
		Class<?> interfaces[] = aClass.getInterfaces();
		for (Class<?> intf : interfaces) {
			aResult.add(intf);
			getAllInterfaces(intf, aResult);
		}
	}

	private void getAllSuperTypes(Class<?> aClass, List<Class<?>> aResult) {
		Class<?> clz = aClass.getSuperclass();
		while (clz != null && !clz.equals(Object.class)) {
			aResult.add(clz);
			clz = clz.getSuperclass();
		}
	}

	
	/**
	 * returns boolean to indicate if given class implemenst or extends the aTestForClass
	 * @param aSourceClass
	 * @param aTestForClass
	 * @return
	 */
	public boolean hasType(Class<?> aSourceClass, Class<?> aTestForClass) {
		Class<?> clazzes[] = getAllSuperTypes(aSourceClass);
		for (Class<?> clz : clazzes) {
			if (clz.getName().equals(aTestForClass.getName())) {
				return true;
			}
		}
		return false;
	}
	

	/**
	 * returns boolean to indicate if given class implements the aTestForClass
	 * @param aSourceClass
	 * @param aTestForClass
	 * @return
	 */
	public boolean hasInterface(Class<?> aSourceClass, Class<?> aTestForClass) {
		Class<?> clazzes[] = getAllInterfaces(aSourceClass);
		for (Class<?> clz : clazzes) {
			if (clz.getName().equals(aTestForClass.getName())) {
				return true;
			}
		}
		return false;
	}

	

	/**
	 * return all Field(s) (properties) of the given class
	 * @param aSourceClass
	 * @return
	 */
	public List<Field> getAllFields(Class<?> aSourceClass) {
		List<Field> result = new ArrayList<Field>();

		Field fields[] = aSourceClass.getDeclaredFields();
		for (Field field : fields) {
			result.add(field);
		}

		return result;
	}

	/**
	 * This returns (not null) list of all primitives (int, boolean etc) and Enums and  
	 * java classes (String, BigDecimal etc, but NOT collections) 
	 * 
	 * @param aSourceClass
	 * @return
	 */
	public List<Field> getAllSimpleFields(Class<?> aSourceClass) {
		List<Field> result = new ArrayList<Field>();

		Field fields[] = aSourceClass.getDeclaredFields();
		for (Field field : fields) {
			if (isSimpleField(field)) {
				result.add(field);
			}
		}

		return result;
	}

	/**
	 * this returns (not null) list of all one2many + many2one + many2many fields of the given class
	 * 
	 * @param aSourceClass
	 * @return
	 */
	public List<Field> getAllRelations(Class<?> aSourceClass) {
		List<Field> result = getAllManyToOneFields(aSourceClass);
		result.addAll(getAllOneToManyFields(aSourceClass));
		result.addAll(getAllManyToManyFields(aSourceClass));
		result.addAll(getAllOneToOneFields(aSourceClass));
		return result;
	}

	/**
	 * this returns (not null) list of all one2many + many2one + many2many fields of the current class
	 * 
	 * @return
	 */
	public List<Field> getAllRelations() {
		return getAllRelations(currentClass());
	}
	
	public List<Class<?>> getAllImportedClasses() {
		return getAllImportedClasses(currentClass());
	}

	/** 
	 * given the source class, return all the non-simplme classes that are being used by this class.
	 * 
	 * @param aSourceClass
	 * @return
	 */
	public List<Class<?>> getAllImportedClasses(Class<?> aSourceClass) {
		List<Class<?>> result = new ArrayList<Class<?>>();
		for (Field fld : getAllFields(aSourceClass)) {
			if (isEnum(fld) || !isSimpleField(fld)) {
				result.add(fld.getType());
			}
		}
		return result;
  	}

	/**
	 * this returns (not null) list of all many2one fields
	 * 
	 * @param aSourceClass
	 * @return
	 */
	public List<Field> getAllManyToOneFields(Class<?> aSourceClass) {
		List<Field> result = new ArrayList<Field>();

		Field fields[] = aSourceClass.getDeclaredFields();
		for (Field field : fields) {
			if (isManyToOneField(field)) {
				result.add(field);
			}
		}

		return result;
	}


	/**
	 * this returns (not null) list of all one2many fields
	 * 
	 * @param aSourceClass
	 * @return
	 */
	public List<Field> getAllOneToManyFields(Class<?> aSourceClass) {
		List<Field> result = new ArrayList<Field>();

		Field fields[] = aSourceClass.getDeclaredFields();
		for (Field field : fields) {
			if (isOneToManyField(field)) {
				result.add(field);
			}
		}

		return result;
	}

	/**
	 * this returns (not null) list of all many2many fields
	 * 
	 * @param aSourceClass
	 * @return
	 */
	public List<Field> getAllManyToManyFields(Class<?> aSourceClass) {
		List<Field> result = new ArrayList<Field>();

		Field fields[] = aSourceClass.getDeclaredFields();
		for (Field field : fields) {
			if (isManyToManyField(field)) {
				result.add(field);
			}
		}

		return result;
	}

	/**
	 * this returns (not null) list of all many2many fields
	 * 
	 * @param aSourceClass
	 * @return
	 */
	public List<Field> getAllOneToOneFields(Class<?> aSourceClass) {
		List<Field> result = new ArrayList<Field>();

		Field fields[] = aSourceClass.getDeclaredFields();
		for (Field field : fields) {
			if (isOneToOneField(field)) {
				result.add(field);
			}
		}

		return result;
	}
	
	/**
	 * this returns (not null) list of all many2one and one2one fields
	 * 
	 * @param aSourceClass
	 * @return
	 */
	public List<Field> getAllManyToOneAndOneToOneFields(Class<?> aSourceClass) {
		List<Field> result = getAllManyToOneFields(aSourceClass);
		result.addAll(getAllOneToOneFields(aSourceClass));
		return result;
	}	

	/**
	 * returns true if this field is the 'owner' of the relation. The result is:
	 * OneToMany : true
	 * ManyToOne : false
	 * OneToOne : true if the field also contains @JoinColumn annotation
	 * ManyToMany : TODO
	 * @param aFieldWrapper
	 * @return
	 */
	public boolean isOwner(FieldWrapper aFieldWrapper) {
		if (aFieldWrapper.getField() != null) {
			return isOwner(aFieldWrapper.getField());
		} else {
			return false;
		}
	}

	public boolean isOwner(Field aField) {
		if (hasAnnotation(aField, OneToMany.class)) {
			return true;
		} else if (hasAnnotation(aField, ManyToOne.class)) {
			return false;
		} else if (hasAnnotation(aField, OneToOne.class)) {
			return hasAnnotation(aField, JoinColumn.class);
		} else {
			return false; //TODO 
		}
	}
	
	/**
	 * this returns (not null) list of all Enum fields
	 * 
	 * @param aSourceClass
	 * @return
	 */
	public List<Field> getAllEnums(Class<?> aSourceClass) {
		List<Field> result = new ArrayList<Field>();

		List<Field> allSimpleFields = getAllSimpleFields(aSourceClass);
		for (Field field : allSimpleFields) {
			if (field.getType().isEnum()) {
				result.add(field);
			}
		}

		return result;
	}

	public Annotation getAnnotation(Field aField, Class<?> aAnnotation) {
		return AnnotationsHelper.getAnnotation(aField, aAnnotation);
	}

	public Annotation getAnnotation(Class<?> aClass, Class<?> aAnnotation) {
		return AnnotationsHelper.getAnnotation(aClass, aAnnotation);
	}

	public Annotation getAnnotation(Class<?> aClass, Annotation aAnnotation) {
		return AnnotationsHelper.getAnnotation(aClass, aAnnotation);
	}

	public Annotation getAnnotation(Method aMethod, Class<?> aAnnotation) {
		return AnnotationsHelper.getAnnotation(aMethod, aAnnotation);
	}

	public boolean hasAnnotation(Field aField, Class<?> aAnnotation) {
		return getAnnotation(aField, aAnnotation) != null;
	}

	public boolean hasAnnotation(Field aField, String aFqNameToLookfor) {
		return AnnotationsHelper.getAnnotation(aField, aFqNameToLookfor) != null;
	}

	public boolean hasAnnotation(Class<?> aTargetClass, Class<?> aAnnotation) {
		return AnnotationsHelper.getAnnotation(aTargetClass, aAnnotation) != null;
	}

	public boolean hasAnnotation(Class<?> aTargetClass, String aFqNameToLookfor) {
		return AnnotationsHelper.getAnnotation(aTargetClass, aFqNameToLookfor) != null;
	}

	public boolean hasAnnotation(Method aMethod, Class<?> aAnnotation) {
		return AnnotationsHelper.getAnnotation(aMethod, aAnnotation) != null;
	}

	public boolean hasAnnotation(Method aMethod, String aFqNameToLookfor) {
		return AnnotationsHelper.getAnnotation(aMethod, aFqNameToLookfor) != null;
	}

	public boolean isEnum(Field aField) {
		return (aField.getType().isEnum());
	}

	public boolean isEnum(Class<?> aClass) {
		return aClass.isEnum();
	}
	
	public boolean isCollection(Field aField) {
		return (isCollection(aField.getType()));
	}

	public boolean isCollection(Class<?> aClass) {
		return (getCollectionTypes().contains(aClass.getName()));
	}

	/**
	 * this returns the concrete class of given class
	 * 
	 * <pre>
	 * Set -> HashSet
	 * List -> ArrayList
	 * </pre>
	 * 
	 * @param aCollectionIntf
	 * @return
	 */
	public Class<?> getConcreteClass(Class<?> aClass) {
		if (getConcreteTypes().containsKey(aClass)) {
			return getConcreteTypes().get(aClass);
		} else {
			return aClass;
		}
	}

	public String getImportedConcreteType(Field aField) {
		return getImportedConcreteType(aField.getType(), getGenericType(aField));
	}

	private static final String DONT_SHOW_PROPS[] = new String[] { "parent", "createdAt", "createdBy",
			"modifiedAt", "modifiedBy", "version", "locked", "lockedBy", "lockedAt", };
	private static final String DONT_SHOW_PROPS_AND_ID[] = new String[] { "id", "parent", "createdAt", "createdBy",
		"modifiedAt", "modifiedBy", "version", "locked", "lockedBy", "lockedAt", };

	private static final boolean DONTSHOW_ID = false;
	
	public boolean dontShow(Field aField) {
		return dontShow(aField, DONTSHOW_ID);
	}

	public boolean dontShow(Field aField, boolean doShowId) {
		String props[] = (doShowId) ? DONT_SHOW_PROPS : DONT_SHOW_PROPS_AND_ID;
		for (String s : props) {
			if (s.equalsIgnoreCase(aField.getName())) {
				return true;
			}
		}

		return false;
	}


	public String getAnnotations(Class<?> aClass) {
		logger.error("getAnnotations() methods should be overwritten"); 
		return "getAnnotations() should be overwritten"; 
	}

	public String getAnnotations(Field aField) {
		logger.error("parseAnnotation() methods should be overwritten"); 
		return "parseAnnotation() should be overwritten"; 
	}


	public String getDocumentation() {
		String result = null;

		result = ""; // TODO

		return result;
	}
	
	/**
	 * This return the class that belong the primary key of the given class
	 * @param aClass
	 * @return
	 */
	public Class<?> getIdType(Class<?> aClass) {
		Field pkfld = getIdField(aClass);
		if (pkfld != null) {
			Class<?> result = pkfld.getType();
			ImportHelper.addImport(result);
			return result;
		}

		return Long.class;
	}

	public Class<?> getIdType() {
		return getIdType(currentClass());
	}

	/**
	 * returns the name of the fieldtype that defines the primary key for the given class
	 * @return
	 */
	public String getIdTypeName(Class<?> aClass) {
		return getIdType(aClass).getSimpleName();
	}

	/**
	 * returns the name of the fieldtype that defines the primary key for the current active entity
	 * @return
	 */
	public String getIdTypeName() {
		return getIdType(currentClass()).getSimpleName();
	}

	/**
	 * returns the field that defines the primary key for the given class
	 * @return
	 */
	public Field getIdField(Class<?> aClass) {
		List<Field> fields = getAllSimpleFields(aClass);
		for (Field field : fields) {
			if (AnnotationsHelper.hasAnnotation(field, mda.annotation.jpa.Id.class)) {
				return field;
			}
		}

		return null;
	}

	/**
	 * returns the field that defines the primary key for the current active entity
	 * @return
	 */
	public Field getIdField() {
		return getIdField(currentClass());
	}

	/**
	 * return the fieldname of the field that defines the primary
	 * @param aClass
	 * @return
	 */
	public String getIdName(Class<?> aClass) {
		Field pkfld = getIdField(aClass);
		if (pkfld != null) {
			return pkfld.getName();
		}

		return "id";
	}

	public String getIdName() {
		return getIdName(currentClass());
	}

	public boolean hasExplicitId(Class<?> aClass) {
		return getIdField(aClass) != null;
	}

	public boolean hasExplicitId() {
		return getIdField(currentClass()) != null;
	}
	
	public boolean isIdField(Field aField) {
		return hasAnnotation(aField, Id.class);
	}

	public boolean isEntity(Class<?> aClass) {
		return hasType(aClass, IEntityType.class);
	}
	
	public boolean isDto(Class<?> aClass) {
		return hasType(aClass, IDtoType.class);
	}
	
	public boolean isService(Class<?> aClass) {
		return hasType(aClass, IServiceType.class);
	}
	

	public boolean isEntity(Field aField) {
		return hasType(getGenericOrActualType(aField), IEntityType.class);
	}
	
	public boolean isEntity(FieldWrapper aFieldWrapper) {
		if (aFieldWrapper.getField() != null) {
			return hasType(getGenericOrActualType(aFieldWrapper.getField()), IEntityType.class);	
		} else {
			return false;
		}
	}
	/**
	 * use this method if no explicit @Id field exists
	 * 
	 * @return
	 */
	public String generateIdField() {
		ImportHelper.addImport("javax.persistence.Id");
		ImportHelper.addImport("javax.persistence.GeneratedValue");
		ImportHelper.addImport("javax.persistence.GenerationType");
		String result = "\t@Id\n";
		result += "\t@GeneratedValue(strategy = GenerationType.AUTO) \n";
		result += "\tprivate Long id;\n";
		return result;
	}

	public String generateIdGetterSetter() {
		if (hasExplicitId(currentClass())) {
			return generateExplicitIdGetterSetter();
		} else {
			return generateStdIdGetterSetter();
		}
	}

	/**
	 * use this method if no an explicit @Id is provided.
	 * 
	 * @return
	 */
	private String generateExplicitIdGetterSetter() {
		Class<?> clz = currentClass();
		String explicitIdName = getIdName(clz);
		String explicitIdType = getIdTypeName(clz);
		StringBuffer sb = new StringBuffer();
		sb.append("\tpublic " + explicitIdType + " "  + explicitIdGetter() + " {\n");
		sb.append("\t\treturn this." + explicitIdName + ";\n");
		sb.append("\t}\n");
		sb.append("\tpublic void set" +  nu.capName(explicitIdName) + "(" + explicitIdType + " aValue) {\n");
		sb.append("\t\t this." + explicitIdName + " = aValue;\n");
		sb.append("\t}\n");

		return sb.toString();
	}

	/**
	 * use this method if no explicit @Id field exists. TODO make more
	 * 'plugable' (using ini file?)
	 * 
	 * @return
	 */
	private String generateStdIdGetterSetter() {
		StringBuffer sb = new StringBuffer();
		sb.append("\tpublic Long getId() {\n");
		sb.append("\t\t" + "return id;\n");
		sb.append("\t}\n");
		sb.append("\tpublic void setId(Long aValue) {\n");
		sb.append("\t\t" + "id = aValue;\n");
		sb.append("\t}\n\n");
		return sb.toString();
	}
	
	public String idGetter() {
		if (hasExplicitId(currentClass())) {
			return explicitIdGetter();
		} else {
			return "getId()";
		}		
	}
	
	private String explicitIdGetter() {
		Class<?> clz = currentClass();
		String explicitIdName = getIdName(clz);
		return "get" + nu.capName(explicitIdName) + "()";
	}
	

	/**
	 * return all subclasses of the current active element.
	 * 
	 * @see SubClassHelper
	 * @return
	 */
	public List<Class<?>> getSubClasses() {
		return SubClassesHelper.getAllSubClasses(currentClass());
	}

	/**
	 * return all subclasses of the given class.
	 * 
	 * @see SubClassHelper
	 * @return
	 */
	public List<Class<?>> getSubClasses(Class<?> aClass) {
		return SubClassesHelper.getAllSubClasses(aClass);
	}

	/**
	 * return the superclass of the current active element. Note it will NOT
	 * return the obvious Object.class, if the superclass is Object.class then
	 * null is returned
	 * 
	 * @return
	 */
	public Class<?> getSuperClass() {
		return getSuperClass(currentClass());
	}

	/**
	 * return the superclass of the given class. Note it will NOT return the
	 * obvious Object.class, if the superclass is Object.class then null is
	 * returned
	 * 
	 * @return
	 */
	public Class<?> getSuperClass(Class<?> aClass) {
		Class<?> result = aClass.getSuperclass();
		if (result != Object.class) {
			return result;
		} else {
			return null;
		}
	}

	/**
	 * Does this class has a SubClass ?
	 * 
	 * @param aClass
	 * @return
	 */
	public boolean hasSubClasses(Class<?> aClass) {
		return getSubClasses(aClass) != null && getSubClasses(aClass).size() > 0;
	}

	/**
	 * Does this class has a SuperClass, other then Object
	 * 
	 * @param aClass
	 * @return
	 */
	public boolean hasSuperClass(Class<?> aClass) {
		return getSuperClass(aClass) != null;
	}

	private static final String IMPLEMENTS = " implements ";

	public String getExtendsAndImplements(Class<?> aClass, Class<?> aDefaultImplements[]) {
		if (aDefaultImplements != null) {
			String impls[] = new String[aDefaultImplements.length];
			for (int i = 0; i < aDefaultImplements.length; i++) {
				impls[i] = aDefaultImplements[i].getName();
			}
			return getExtendsAndImplements(aClass, impls);
		} else {
			return getExtendsAndImplements(aClass, new String[] {});
		}
	}

	public String getExtendsAndImplements(Class<?> aClass, String aDefaultImplements[]) {
		String result = "";

		Class<?> supercls = aClass.getSuperclass();
		if (supercls != null && !supercls.equals(Object.class)) {
			result = "extends " + supercls.getSimpleName();
			ImportHelper.addImport(supercls);
		}

		Class<?> intfs[] = aClass.getInterfaces();
		if (intfs.length > 0) {
			for (Class<?> intf : intfs) {
				if (!isCodegenInterface(intf)) {
					if (result.indexOf(IMPLEMENTS) < 0) {
						result += IMPLEMENTS;
					}
					ImportHelper.addImport(intf);
					if (!result.endsWith(IMPLEMENTS)) {
						result += ", ";
					}
					result += intf.getSimpleName();
				}
			}
		}

		if (aDefaultImplements != null && aDefaultImplements.length > 0) {
			if (result.indexOf(IMPLEMENTS) < 0) {
				result += IMPLEMENTS;
			}
			for (int i = 0; i < aDefaultImplements.length; i++) {
				String impl = aDefaultImplements[i];
				ImportHelper.addImport(impl);
				result += new NameUtils().getSimplename(impl);
				if (i < aDefaultImplements.length - 1) {
					result += ",";
				}
			}
		}
		return result;
	}

	/**
	 * This returns the Field with the given fieldname in the given class, or null if not found
	 * @param aClass
	 * @return
	 */
	public Field getFieldByName(Class<?> aClass, String aFieldname) {
		for (Field fld : getAllFields(aClass)) {
			if (fld.getName().equals(aFieldname)) return fld;
		}
		return null;
	}
	
	private boolean isCodegenInterface(Class<?> aIntf) {
		return aIntf.getName().startsWith("mda.");
	}

	/**
	 * return the main 'kind' of a field
	 * 
	 * @param aField
	 * @return FieldKind
	 */
	public FieldKind getFieldKind(Field aField) {
		if (isPrimitive(aField)) {
			return FieldKind.PRIMITIVE;
		} else if (isEnum(aField)) {
			return FieldKind.ENUM;
		} else if (isSimpleField(aField)) {
			return FieldKind.SIMPLE;
		} else if (isOneToManyField(aField)) {
			return FieldKind.ONE2MANY;
		} else if (isManyToOneField(aField)) {
			return FieldKind.MANY2ONE;
		} else {
			return FieldKind.COMPOSITE;
		}
	}

	/**
	 * return the sub 'kind' of a field
	 * 
	 * @param aField
	 * @return FieldKind
	 */
	public FieldSimpleKind getFieldSimpleKind(Field aField) {
		String typename = aField.getType().getName().toLowerCase();
		FieldKind kind = getFieldKind(aField);

		String primNrs[] = new String[] { "int", "short", "long" };
		String primDec[] = new String[] { "float", "double" };
		String simpleNrs[] = new String[] { "integer", "short", "long", };
		String simpleDec[] = new String[] { "long", "float", "double", "bigdecimal" };

		if (isStringType(aField.getType())) {
			return FieldSimpleKind.STRING;
		} else if (FieldKind.PRIMITIVE.equals(kind)) {
			return getSimpleKindFromPrimitive(aField, typename, primNrs, primDec);
		} else if ((FieldKind.SIMPLE.equals(kind))) {
			return getSimpleKindForSimple(aField, typename, simpleNrs, simpleDec);
		} else if ((FieldKind.ENUM.equals(kind))) {
			return FieldSimpleKind.ENUM;
		} else {
			return FieldSimpleKind.OTHER;
		}
	}

	private FieldSimpleKind getSimpleKindForSimple(Field aField, String typename, String[] simpleNrs, String[] simpleDec) {
		if (typename.indexOf("string") >= 0) {
			return FieldSimpleKind.STRING;
		}
		if (typename.indexOf("bool") >= 0) {
			return FieldSimpleKind.BOOL;
		}
		if (typename.indexOf("date") >= 0) {
			return FieldSimpleKind.DATETIME;
		}

		for (String s : simpleNrs) {
			if (typename.indexOf(s) >= 0) {
				return FieldSimpleKind.NUMBER;
			}
		}
		for (String s : simpleDec) {
			if (typename.indexOf(s) >= 0) {
				return FieldSimpleKind.DECIMAL;
			}
		}
		System.out.println("** unknown FieldSimpleKind " + aField);
		return null;
	}

	private FieldSimpleKind getSimpleKindFromPrimitive(Field aField, String typename, String[] primNrs, String[] primDec) {
		for (String s : primNrs) {
			if (typename.indexOf(s) >= 0) {
				return FieldSimpleKind.NUMBER;
			}
		}
		for (String s : primDec) {
			if (typename.indexOf(s) >= 0) {
				return FieldSimpleKind.DECIMAL;
			}
		}
		if (typename.indexOf("bool") >= 0) {
			return FieldSimpleKind.BOOL;
		}
		System.out.println("** unknown FieldSimpleKind " + aField);
		return null;
	}

	/**
	 * Return true if this property is annotated with @JoinTable
	 * 
	 * @param aField
	 * @return
	 */
	public boolean isJoinTable(Field aField) {
		if (hasAnnotation(aField, JoinTable.class)) {
			return true;
		} else {
			if (hasAnnotation(aField, OneToMany.class)) {
				OneToMany anno = (OneToMany) getAnnotation(aField, OneToMany.class);
				// TODO other scenario
				return (anno.mappedBy() != null && anno.mappedBy().trim().length() > 0);
			} else if (hasAnnotation(aField, ManyToMany.class)) {
				return true;
			}
			return false;
		}
	}

	public String getOneToManyType(Field aField, Class<?> aParent) {
		return "todo";
	}

	public String[] getEnumItems(Class<?> aClass) {
		if (aClass != null && aClass.isEnum()) {
			try {
				Method m = aClass.getMethod("values", new Class<?>[] {});
				Object values[] = (Object[]) m.invoke(null, new Object[] {});
				if (values != null && values.length > 0) {
					String result[] = new String[values.length];
					for (int i = 0; i < values.length; i++) {
						result[i] = values[i].toString();
					}
					return result;
				}
			} catch (Exception e) {
				logger.error("error in getEnumItems()", e);
			}
		}
		return new String[] { "???" };
	}

	public boolean hasAbstractAnnotation() {
		Class<?> clz = currentClass();

		return (hasAnnotation(clz, mda.annotation.Abstract.class));
	}

	public String getSerialVersionUID() {
		return "" + new Date().getTime() + "L";
	}

	/**
	 * Use this method if you want to force that the given class is generated
	 * with the given template, even though this template does not apply to this
	 * class initially.
	 * 
	 * @param aClass
	 * @param aTemplate
	 */
	public void generate(Class<?> aClass, ITemplate aTemplate, Object ... args) {
		AdditionalGeneratesHelper.add(aClass, aTemplate, args);
	}

	/**
	 * use this method if you want to include generated code from a jet template
	 * indicated with the snippet class. The second param is the argument that
	 * is passed to this jet template. The major difference with the
	 * <b>generate</b> method is that this is not called via an ITemplate but
	 * with class of the jet file.
	 * 
	 * @param aFqClassname
	 * @param aArgument
	 * @return
	 */
	public String include(Class<?> aSnippetClass, Object... aArguments) {
		try {
			Method method = aSnippetClass.getMethod("generate", new Class[] { Object.class });
			Object generator = aSnippetClass.newInstance();
			Object gensrcObj = method.invoke(generator, new Object[] { aArguments });
			String result = (gensrcObj != null) ? gensrcObj.toString() : "";
			return result;
		} catch (InvocationTargetException ex) {
			logger.error("error while include() code for " + aSnippetClass.getName() + " " + ex.getTargetException());
			return "**error**";
		} catch (Throwable t) {
			logger.error("error while include() code for " + aSnippetClass.getName() + " " + t);
			return "**error**";
		}
	}

	public String include(Class<?> aSnippetClass, JetArgument arg) {
		try {
			Method method = aSnippetClass.getMethod("generate", new Class[] { Object.class });
			Object generator = aSnippetClass.newInstance();
			Object gensrcObj = method.invoke(generator, new Object[] { arg });
			String result = (gensrcObj != null) ? gensrcObj.toString() : "";
			return result;
		} catch (InvocationTargetException ex) {
			logger.error("error while include() code for " + aSnippetClass.getName() + " " + ex.getTargetException());
			return "**error**";
		} catch (Throwable t) {
			logger.error("error while include() code for " + aSnippetClass.getName() + " " + t);
			return "**error**";
		}
	}

	
	/**
	 * return true if this Field is required
	 * 
	 * @param aField
	 * @return
	 */
	public boolean isRequired(Field aField) {
		return vu.isRequired(aField);
	}

	public boolean isRequired(FieldWrapper aFieldWrapper) {
		Field fld = aFieldWrapper.getField();
		if (fld != null) {
			return isRequired(fld);
		} else {
			return false;
		}
	}

	/**
	 * return a valid classname given a Class
	 * This method also takes into account an Array class. Such a class would normally return something like "[Lcom.xxx.Aclasname;"
	 * @param aClass
	 * @return
	 */
	public String getClassname(Class<?> aClass) {
		String result = aClass.getCanonicalName();
		if (result.endsWith("[]")) {
			result = result.substring(0, result.length() - 2);
		}
		return result;
	}
	
	public String getSimpleClassname(Class<?> aClass) {
		String result = getClassname(aClass);
		if (result.indexOf(".") > 0) {
			result = result.substring(result.lastIndexOf(".") + 1);
		}
		return result;
	}
	
	/**
	 * shortcut to get the base-package string 
	 * @see DataStore.getBasePackage()
	 * @return
	 */
	public String getBasePackage() {
		return DataStore.getInstance().getBasePackage();
	}
	
	/**
	 * shortcut to get the (user) supplied App-package string 
	 * @see DataStore.getBasePackage()
	 * @return
	 */
	public String getAppPackage() {
		return fnh.getOutputAppPackage();
	}	
	
	/**
	 * replace the base-package with app-package in the given input string
	 * @param aPackage
	 * @return
	 */
	public String substitutePackage(String aString) {
		return fnh.formatPackage(aString);
	}
		
	/**
	 * return the one class that implements the ApplicationBaseType interface
	 * @return
	 */
	public ApplicationBaseType getApplicationBaseType() {
		return ModelClassesUtils.findApplicationType();
	}
	
	/**
	 * returns the Class that is currenly active
	 * @return
	 */
	public Class<?> currentClass() {
		return JetArgument.getCurrent().getElementClass();
	}
	
	/**
	 * returns the Table name that belong to the current active class.
	 * If the class is annotated with @Table and the name is provided, this will be returned else the classname is returned
	 * @return
	 */
	public String getTableName() {
		return getTableName(currentClass());
	}
	
	/**
	 * returns the Table name that belong to given class.
	 * @see getTableName()
	 * @param aClass
	 * @return
	 */
	public String getTableName(Class<?> aClass) {
		String result = aClass.getSimpleName();
		if (hasAnnotation(aClass, Table.class)) {
			Table anno = (Table) getAnnotation(aClass, Table.class);
			if (anno.name() != null && !anno.name().isEmpty()) result = anno.name();
		}
		return result;
	}

	/**
	 * returns the Column name that belong to given Field.
	 * If the field is annotated with @Column and the name is provided, this will be returned else the fieldname is returned
	 * @param aClass
	 * @return
	 */
	public String getColumnName(Field aField) {
		String result = aField.getName();
		if (hasAnnotation(aField, Column.class)) {
			Column anno = (Column) getAnnotation(aField, Column.class);
			if (anno.name() != null && !anno.name().isEmpty()) result = anno.name();
		}
		return result;
	}

	/**
	 * @see AnnotationsHelper.getMappedByFkField
	 * @param aField
	 * @return
	 */
	public boolean hasMappedByFkField(Field aField) {
		return AnnotationsHelper.hasMappedByFkField(aField);
	}
	
	/**
	 * @see AnnotationsHelper.getMappedByFkField
	 * @param aFieldWrapper
	 * @return
	 */
	public boolean hasMappedByFkField(FieldWrapper aFieldWrapper) {
		return AnnotationsHelper.hasMappedByFkField(aFieldWrapper.getField());
	}
	
	/**
	 * @see AnnotationsHelper.getMappedByFkField
	 * @param aField
	 * @return
	 */
	public Field getMappedByFkField(Field aField) {
		return AnnotationsHelper.getMappedByFkField(aField);
	}
	
	/**
	 * @see AnnotationsHelper.getMappedByFkField
	 * @param aFieldWrapper
	 * @return
	 */
	public Field getMappedByFkField(FieldWrapper aFieldWrapper) {
		return AnnotationsHelper.getMappedByFkField(aFieldWrapper.getField());
	}

	/**
	 * return the name mappedBy foreign key field
	 * @param aFieldWrapper
	 * @return
	 */
	public String getMappedByFkFieldName(FieldWrapper aFieldWrapper) {
		Field fld = getMappedByFkField(aFieldWrapper);
		return (fld == null) ? "??fk??" : fld.getName();
	}
	/**
	 * return the name mappedBy foreign key field
	 * @param aFieldWrapper
	 * @return
	 */
	public String getMappedByFkFieldName(Field aField) {
		Field fld = getMappedByFkField(aField);
		return (fld == null) ? "??fk??" : fld.getName();
	}
	

	/**
	 * this the 'owner' field that corresponds with the given field, or null if not found 
	 * @param aMappedByClass
	 * @param aMappedByField
	 * @return
	 */
	public Field getOwnerMappedByFkField(Field aMappedByField) {
		for (Class<?> clz : getAllModelEntities()) {
			for (Field fld : getAllFields(clz)) {
				if (hasMappedByFkField(fld)) {
					Field mappedby = getMappedByFkField(fld);
					if (mappedby.equals(aMappedByField)) {
						return mappedby;
					}
				}
			}
		}
		return null;
	}

	/**
	 * this the 'owner' class that corresponds with the given field, or null if not found 
	 * @param aMappedByClass
	 * @param aMappedByField
	 * @return
	 */
	public Class<?> getOwnerClassMappedByFkField(Field aMappedByField) {
		for (Class<?> clz : getAllModelEntities()) {
			for (Field fld : getAllFields(clz)) {
				if (hasMappedByFkField(fld)) {
					Field mappedby = getMappedByFkField(fld);
					if (mappedby.equals(aMappedByField)) {
						return clz;
					}
				}
			}
		}
		return null;
	}

	/**
	 * This methods validates the given class, to check if alle annotations are set correctly
	 * @param aClass
	 * @return
	 */
	public boolean validate(Class<?> aClass, List<String> aMessages) {

		boolean status = AnnotationsHelper.validate(aClass, aMessages);
		
		for (Field fld : getAllFields(aClass)) {
			status = status && AnnotationsHelper.validate(fld, aMessages);
		}
		return status;
	}
	

	/**
	 * return all model entity classes
	 * @return
	 */
	public List<Class<?>> getAllModelEntities() {
		return ModelClassesUtils.findModelClassesWithInterface(IEntityType.class);
	}

	/**
	 * return all model service classes
	 * @return
	 */
	public List<Class<?>> getAllModelServices() {
		return ModelClassesUtils.findModelClassesWithInterface(IServiceType.class);
	}
	
	/**
	 * return all model service classes
	 * @return
	 */
	public List<Class<?>> getAllModelEnums() {
		return ModelClassesUtils.findModelEnums();
	}

	/**
	 * This a NestedObjects object that contains a list of all nested objects including the 
	 * class-paths and corresponding field names.
	 * @param aClass
	 * @return NestedObjects
	 */
	public NestedObjects getNestedObjects(Class<?> aClass) {
		return new NestedObjects(aClass);
	}

	public Map<Class<?>, Class<?>> getConcreteTypes() {
		return CONCRETE_TYPES;
	}


	public Set<String> getCollectionTypes() {
		return COLLECTION_TYPES;
	}
	
	/**
	 * retrieve the filled-in value from SubsValue from the DataStore
	 * @param aName
	 * @return
	 */
	public String getSubsvalueFromDataStore(String aName) {
		SubsValue subsval =  DataStore.getInstance().getSubsValue(aName);
		if (subsval != null) {
			return subsval.getValue();
		} else {
			logger.error("No SubsValue found for : " + aName);
			return "?" + aName + "?";
		}
	}
	
	/**
	 * return the current cartrdiges folder.
	 * While running the plugin this is <eclipse-dir>/flca.easymda.generator_xxx/cartridges
	 * While junit testing this is the value returned by the TestTemplatesData interface method: getPluginDir(), eg <easymda>/flca.mda.generator/cartridges
	 * @return
	 */
	public File getCartridgeDir() {
		return new File(DataStore.getInstance().getValue(CodegenConstants.DATASTORE_CARTRIDGE_DIR));
	}

	/**
	 * return the current cartrdiges folder.
	 * While running the plugin this is root dir of the selected class or package
	 * While junit testing this is the value returned by the TestTemplatesData interface method: getModelDir(), eg <easymda>/flca.mda.test.model.webapp
	 * @return
	 */
	public File getModelDir() {
		return new File(DataStore.getInstance().getValue(CodegenConstants.DATASTORE_MODEL_DIR));
	}

	static {
		CONCRETE_TYPES = new HashMap<Class<?>, Class<?>>();
		CONCRETE_TYPES.put(Set.class, HashSet.class);
		CONCRETE_TYPES.put(List.class, ArrayList.class);
		CONCRETE_TYPES.put(Map.class, HashMap.class);
		CONCRETE_TYPES.put(Collection.class, HashSet.class);

		COLLECTION_TYPES = new HashSet<String>();
		COLLECTION_TYPES.add(Set.class.getName());
		COLLECTION_TYPES.add(List.class.getName());
		COLLECTION_TYPES.add(HashSet.class.getName());
		COLLECTION_TYPES.add(ArrayList.class.getName());
		COLLECTION_TYPES.add(Collection.class.getName());
	}
}
